package cn.yto.print.socket;

import cn.hutool.json.JSONUtil;
import cn.yto.print.config.EndpointConfigurator;
import cn.yto.print.config.PrintLinkedContext;
import cn.yto.print.config.ThreadPoolManager;
import cn.yto.print.config.i18n.I18nResourceManager;
import cn.yto.print.constants.AssistantConstants;
import cn.yto.print.controller.IndexController;
import cn.yto.print.enums.MessageTypeEnum;
import cn.yto.print.enums.PrintCmdEnum;
import cn.yto.print.enums.PrintHandlerEnum;
import cn.yto.print.enums.ResponseCodeEnum;
import cn.yto.print.model.request.PrintTaskRequest;
import cn.yto.print.model.response.PrintTaskResponse;
import cn.yto.print.plugins.PluginStatusManager;
import cn.yto.print.service.PrintProduceService;
import cn.yto.print.system.SpringContextHolder;
import cn.yto.print.system.version.KhgjContactManager;
import cn.yto.print.utils.ExpiredMsgUtil;
import cn.yto.print.utils.PrinterUtil;
import cn.yto.print.utils.WebSocketUtil;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

import static cn.yto.print.enums.PrintCmdEnum.getPrintCmd;

@Slf4j
@Component
@ServerEndpoint(value = "/print/{sessionId}", configurator = EndpointConfigurator.class)
public class PrintSocketEndpoint {

    @Value("${app.version}")
    private String appVersion;
    @Autowired
    private PrintProduceService printProduceService;
    @Autowired
    private IndexController indexController;

    @OnOpen
    public void onOpen(Session session, @PathParam(AssistantConstants.SESSION_ID) String sessionId) {
        if (StringUtils.isBlank(sessionId)) {
            sessionId = UUID.randomUUID().toString();
        }
        session.getUserProperties().put(AssistantConstants.SESSION_ID, sessionId);
        PrintLinkedContext.addSession(sessionId, session);
        ExpiredMsgUtil.handleHistoryResponse(session);
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        if (StringUtils.isBlank(message)) {
            return;
        }

        // 当天首次操作上传记录日志
        ThreadPoolManager.getExecutorService().execute(KhgjContactManager::firstLogOperate);
        PrintTaskResponse printTaskResponse = new PrintTaskResponse();

        try {
            PrintTaskRequest printTaskRequest = JSONUtil.toBean(message, PrintTaskRequest.class);
            printTaskRequest.setSessionId((String) session.getUserProperties().get(AssistantConstants.SESSION_ID));
            printTaskResponse.setSessionId(printTaskRequest.getSessionId());
            printTaskResponse.setCmd(printTaskRequest.getCmd());

            PrintCmdEnum printCmd = getPrintCmd(printTaskRequest.getCmd());
            if (null == printCmd) {
                printTaskResponse.setCommProperty(ResponseCodeEnum.UNKNOWN);
                SpringContextHolder.getBean(IndexController.class).addConsoleLog(
                        I18nResourceManager.getInstance().get("index.console.unknown"), MessageTypeEnum.WARNING);
                WebSocketUtil.sendSocketMessage(session, printTaskResponse);
                return;
            }

            switch (printCmd) {
                case PRINT:
                    if (!validatePrintTaskRequest(printTaskRequest, printTaskResponse)) {
                        handleParamErrorResponse(printTaskRequest, printTaskResponse);
                        WebSocketUtil.sendSocketMessage(session, printTaskResponse);
                        return;
                    }
                    printProduceService.pushPrintQueue(printTaskRequest);
                    break;
                case STATUS:
                    getPrintPluginStatus(printTaskResponse);
                    SpringContextHolder.getBean(IndexController.class).addConsoleLog(
                            I18nResourceManager.getInstance().get("index.console.status"), MessageTypeEnum.INFO);
                    WebSocketUtil.sendSocketMessage(session, printTaskResponse);
                    break;
                case GET_PRINTER:
                    getLocalPrinterList(printTaskResponse);
                    SpringContextHolder.getBean(IndexController.class).addConsoleLog(
                            I18nResourceManager.getInstance().get("index.console.getPrinter"), MessageTypeEnum.INFO);
                    WebSocketUtil.sendSocketMessage(session, printTaskResponse);
                    break;
                case VERSION:
                    printTaskResponse.setVersion(appVersion);
                    SpringContextHolder.getBean(IndexController.class).addConsoleLog(
                            I18nResourceManager.getInstance().get("index.console.version"), MessageTypeEnum.INFO);
                    WebSocketUtil.sendSocketMessage(session, printTaskResponse);
                    break;
                default:
                    break;
            }
        } catch (Exception e) {
            log.error("接收前端发来的请求处理异常", e);
            printTaskResponse.setCommProperty(ResponseCodeEnum.EXCEPTION);
            WebSocketUtil.sendSocketMessage(session, printTaskResponse);
        }
    }

    @OnClose
    public void onClose(Session session, CloseReason reason) {
        String sessionId = (String) session.getUserProperties().get(AssistantConstants.SESSION_ID);
        log.info("前端打印通道连接断开，会话ID：{}，状态码：{}，关闭原因：{}", sessionId, reason.getCloseCode(), reason.getReasonPhrase());
        try {
            if (session.isOpen()) {
                session.close();
            }
        } catch (Exception e) {
            log.error("前端打印通道关闭连接发生异常", e);
        }
        PrintLinkedContext.removeSession(sessionId);
        String message = String.format(I18nResourceManager.getInstance().get("index.console.break"), PrintLinkedContext.getSessionSize());
        indexController.addConsoleLog(message, MessageTypeEnum.ERROR);
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        log.error("前端打印通道连接发生异常", throwable);
    }

    /**
     * 校验打印任务请求参数
     */
    public static boolean validatePrintTaskRequest(PrintTaskRequest printTaskRequest, PrintTaskResponse printTaskResponse) {
        if (StringUtils.isBlank(printTaskRequest.getTaskId())) {
            log.error("打印参数[taskId]不能为空");
            printTaskResponse.setCommProperty(ResponseCodeEnum.PARAM_ERROR);
            SpringContextHolder.getBean(IndexController.class).addConsoleLog(I18nResourceManager.getInstance().get("index.console.taskId"), MessageTypeEnum.ERROR);
            return false;
        }

        if (StringUtils.isBlank(printTaskRequest.getPrinter())) {
            String defaultPrinterName = PrinterUtil.getDefaultPrinter();
            if (StringUtils.isEmpty(defaultPrinterName)) {
                log.error("打印参数[printer]不能为空");
                printTaskResponse.setCommProperty(ResponseCodeEnum.PARAM_ERROR);
                SpringContextHolder.getBean(IndexController.class).addConsoleLog(I18nResourceManager.getInstance().get("index.console.printer"), MessageTypeEnum.ERROR);
                return false;
            }
            printTaskRequest.setPrinter(defaultPrinterName);
        }

        if (CollectionUtils.isEmpty(printTaskRequest.getDocuments())) {
            log.error("打印参数[documents]不能为空");
            printTaskResponse.setCommProperty(ResponseCodeEnum.PARAM_ERROR);
            SpringContextHolder.getBean(IndexController.class).addConsoleLog(I18nResourceManager.getInstance().get("index.console.documents"), MessageTypeEnum.ERROR);
            return false;
        } else {
            List<PrintHandlerEnum> printHandlerEnumList = new ArrayList<>();
            for (PrintTaskRequest.Document document : printTaskRequest.getDocuments()) {
                if (null == document || StringUtils.isBlank(document.getExpressPrintType())) {
                    log.error("打印参数[expressPrintType]不能为空");
                    printTaskResponse.setCommProperty(ResponseCodeEnum.PARAM_ERROR);
                    SpringContextHolder.getBean(IndexController.class).addConsoleLog(I18nResourceManager.getInstance().get("index.console.expressPrint"), MessageTypeEnum.ERROR);
                    return false;
                }
                PrintHandlerEnum printHandlerEnum = PrintHandlerEnum.getPrintTypeEnum(document.getExpressPrintType());
                if (null == printHandlerEnum) {
                    log.error("打印参数[expressPrintType]类型不支持");
                    printTaskResponse.setCommProperty(ResponseCodeEnum.PARAM_ERROR);
                    SpringContextHolder.getBean(IndexController.class).addConsoleLog(I18nResourceManager.getInstance().get("index.console.expressType"), MessageTypeEnum.ERROR);
                    return false;
                }
                printHandlerEnumList.add(printHandlerEnum);
            }
            // 检测打印组件是否连接
            List<PrintHandlerEnum> printHandlerList = printHandlerEnumList.stream().distinct().collect(Collectors.toList());
            Map<String, Integer> pluginStatus = PluginStatusManager.getInstance().getPluginStatus();
            for (PrintHandlerEnum printHandlerEnum : printHandlerList) {
                if (AssistantConstants.OPENED != pluginStatus.get(printHandlerEnum.getName())) {
                    printTaskResponse.setCodeProperty(ResponseCodeEnum.UNLINK_ERROR);
                    printTaskResponse.setMsg(printHandlerEnum.getDesc() + "打印组件未连接或连接异常~");
                    log.error("{} 打印组件未连接或连接异常", printHandlerEnum.getDesc());
                    String console = I18nResourceManager.getInstance().get(printHandlerEnum.getLocale()) + I18nResourceManager.getInstance().get("index.console.unlink");
                    SpringContextHolder.getBean(IndexController.class).addConsoleLog(console, MessageTypeEnum.ERROR);
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 处理参数错误返回结果
     */
    public static void handleParamErrorResponse(PrintTaskRequest printTaskRequest, PrintTaskResponse printTaskResponse) {
        printTaskResponse.setCmd(PrintCmdEnum.NOTIFY_PRINT_RESULT.getCmd());
        printTaskResponse.setTaskId(printTaskRequest.getTaskId());
        printTaskResponse.setPrinter(printTaskRequest.getPrinter());
        printTaskResponse.setTemplateType(printTaskRequest.getTemplateType());
        List<PrintTaskResponse.PrintStatus> printStatusList = Lists.newArrayList();
        printTaskRequest.getDocuments().forEach(document -> {
            PrintTaskResponse.PrintStatus printStatus = new PrintTaskResponse.PrintStatus();
            printStatus.setDocumentID(document.getDocumentID());
            printStatus.setStatus(ResponseCodeEnum.FAILURE.getStatus());
            printStatusList.add(printStatus);
        });
        printTaskResponse.setPrintStatus(printStatusList);

        printTaskResponse.setSuccess(Lists.newArrayList());
        printTaskResponse.setTotal(printTaskRequest.getDocuments().size() + "-0");
    }

    public static void getPrintPluginStatus(PrintTaskResponse printTaskResponse) {
        Map<String, Integer> pluginStatus = PluginStatusManager.getInstance().getPluginStatus();
        List<String> pluginConnectionSuccess = new ArrayList<>();
        List<String> pluginConnectionError = new ArrayList<>();
        pluginStatus.forEach((key, value) -> {
            if (value == AssistantConstants.OPENED) {
                pluginConnectionSuccess.add(key);
            } else {
                pluginConnectionError.add(key);
            }
        });
        printTaskResponse.setPluginConnectionSuccess(pluginConnectionSuccess);
        printTaskResponse.setPluginConnectionError(pluginConnectionError);
        printTaskResponse.setCommProperty(ResponseCodeEnum.SUCCESS);
    }

    /**
     * 打印组件连接状态回执
     */
    public static void handlePluginStatusAll() {
        PrintTaskResponse printTaskResponse = new PrintTaskResponse();
        printTaskResponse.setCmd(PrintCmdEnum.STATUS.getCmd());
        getPrintPluginStatus(printTaskResponse);
        printTaskResponse.setCommProperty(ResponseCodeEnum.PLUGIN_STATUS);

        Map<String, Session> sessionMap = PrintLinkedContext.getAllSession();
        if (sessionMap.size() > 0) {
            log.info("打印组件连接状态变更，通知 {} 个连接到的小助手的客户端", sessionMap.size());
            for (Map.Entry<String, Session> entry : sessionMap.entrySet()) {
                WebSocketUtil.sendSocketMessage(entry.getValue(), printTaskResponse);
            }
        }
    }


    /**
     * 获取本地打印机列表
     */
    public static void getLocalPrinterList(PrintTaskResponse printTaskResponse) {
        List<Map<String, String>> printerList = PrinterUtil.getPrinterList();
        if (CollectionUtils.isEmpty(printerList)) {
            printTaskResponse.setCommProperty(ResponseCodeEnum.NO_PRINTER);
        } else {
            printTaskResponse.setCommProperty(ResponseCodeEnum.SUCCESS);
            printTaskResponse.setPrinters(printerList);
        }
    }


}
